package org.easiermock;

import java.util.HashMap;
import java.util.Map;

import org.aopalliance.intercept.MethodInvocation;

/**
 * A <code>MethodListener</code> that keeps a count of method-level operations. 
 * The methods are identified by a String ID. Different
 * methods can share the same ID and the counts will be the sum of all of the
 * operations of that type on all methods that share the ID.
 * 
 * <p>In the standard implementation the ID is assigned to a method via 
 * the Mock annotation's id attribute. When the Mock annotation is 
 * specified on the class all methods are assigned the same ID unless 
 * the method is also annotated.
 * 
 * @author Neil Fuller
 */
public class CountingMethodListener implements MethodListener {

	private Map<String,MethodCounter> methodCounters = new HashMap<String,MethodCounter>();
	
	/**
	 * Increments the count for the called method 
	 */
	public void beforeInvocation(MethodInvocation methodInvocation) {
		String id = getMethodId(methodInvocation);
		if (id != null) {
			MethodCounter counter = getCounterForId(id);
			counter.incrementBefore();
		}
	}

	/**
	 * Return the current count for methods with the specified ID
	 * 
	 * @param id the ID of the method
	 * @return the count
	 */
	public int getBeforeCount(String id) {
		MethodCounter counter = methodCounters.get(id);
		return counter != null ? counter.getBeforeCount() : 0;
	}

	/**
	 * Reset the count for all methods.
	 */
	public void resetAll() {
		synchronized(methodCounters) {
			methodCounters.clear();
		}
	}

	/**
	 * Reset the count for the specified ID
	 * 
	 * @param id the ID
	 */
	public void reset(String id) {
		synchronized(methodCounters) {
			methodCounters.remove(id);
		}
	}

	/**
	 * Retrieves the ID for the method being invoked, as represented by the supplied MethodInvocation
	 * object.
	 * 
	 * @param methodInvocation the method invocation
	 * @return the method ID or null if the ID cannot be determined
	 */
	private String getMethodId(MethodInvocation methodInvocation) {
		Mock mock = AnnotationUtil.getMockMethodAnnotation(methodInvocation.getMethod());
		String id = null;
		if (mock != null) {
			id = mock.id();
			if ("".equals(id)) {
				id = methodInvocation.getMethod().getName();
			}
		}
		return id;
	}
	
	/**
	 * Returns the MethodCounter for the method with the specified ID.
	 * If one cannot be found, one will be created and returned.
	 * 
	 * @param id the ID
	 * @return the MethodCounter
	 */
	private MethodCounter getCounterForId(String id) {
		MethodCounter counter;
		synchronized(methodCounters) {
			counter = methodCounters.get(id);
			if (counter == null) {
				counter = new MethodCounter();
				methodCounters.put(id, counter);
			} 
		}
		return counter;
	}

	/**
	 * An method-level counter
	 * 
	 * @author Neil Fuller
	 */
	static class MethodCounter {
		private int before;
		
		public synchronized int getBeforeCount() {
			return before;
		}
		
		public synchronized void incrementBefore() {
			this.before++;
		}
	}
}
